#include "GTaskManager.h"
#include "GWorkerThread.h"
#include "GTaskQueue.h"
#include "GParallelForInternal.h"
#include "GParallelForTask.h"

namespace GEngine
{

	GParallelForInternal::GParallelForInternal(GTaskResult* Result, GParallelForTask* ForTask, const GTaskRange& ForRange) : 
		GTask(Result ,G_TP_DEFAULT, true), Task(ForTask), Range(ForRange)
	{
	}

	GParallelForInternal::~GParallelForInternal()
	{
	}

	// Spread the job across queues
	bool GParallelForInternal::Spread(GTaskQueue** QueueArr, GSize QueueCount)
	{
		GSize First = Range.GetIndexFirst();
		GSize Last = Range.GetIndexLast();
		GSize Gran = Range.GetGranularity();
		GSize Diff = Last - First;

		// Decide how many tasks we can spawn
		GSize BatchCount = (Diff) / Gran;
		
		// Check if range is divideable
		if (BatchCount == 0)
		{
			return false;
		}
		
		// Do not create more than worker/queue count
		BatchCount = (BatchCount > QueueCount) ? QueueCount : BatchCount;

		// Decide each task range
		GSize BatchSize = (Diff) / BatchCount;

		// Track last task to modify the end range
		GParallelForInternal* LastTask = NULL;

		// Spawn tasks
		for (GSize I = 0; I < BatchCount; ++I)
		{
			GTaskRange NewRange(First, First + BatchSize, Gran);
			LastTask = new GParallelForInternal(Result, Task, NewRange);

			// Push into queue
			QueueArr[I]->Push(LastTask);

			// We skipped scheduling so
			// Increase referance manually
			IncreaseRef();

			First += BatchSize;
		}

		// Modify the end range
		LastTask->Range.TrimBack(Last);

		return true;
	}

	bool GParallelForInternal::Reduce(OUTPUT GEngine::GTask** OutTask)
	{
		GSize First = Range.GetIndexFirst();
		GSize Last = Range.GetIndexLast();
		GSize Gran = Range.GetGranularity();

		// Can we reduce it ?
		if ((Last - First) <= (2 * Gran))
		{
			return false;
		}

		// Calculate new end
		GSize CutOff = Last - Gran;

		GTaskRange NewRange(CutOff, Last, Gran);
		*OutTask = new GParallelForInternal(Result, Task, NewRange);

		// We skipped scheduling so
		// Increase referance manually
		IncreaseRef();

		// Reduce my end
		Range.TrimBack(CutOff);

		// Create a new ParallelFor task with half of my range
		return true;
	}
	
	bool GParallelForInternal::Divide(OUTPUT GEngine::GTask** OutTask)
	{
		GSize First = Range.GetIndexFirst();
		GSize Last = Range.GetIndexLast();
		GSize Gran = Range.GetGranularity();

		// Can we divide it ?
		if ((Last - First) <= (2 * Gran))
		{
			return false;
		}

		// Calculate middle index
		GSize Half = First + ((Last - First) / 2);

		GTaskRange NewRange(Half, Last, Gran);
		(*OutTask) = new GParallelForInternal(Result, Task, NewRange);
		
		// We skipped scheduling so
		// Increase referance manually
		IncreaseRef();

		// Reduce my end
		Range.TrimBack(Half);

		// Create a new ParallelFor task with half of my range
		return true;
	}

	GUInt32 GParallelForInternal::Execute(GSize WorkerId)
	{
		
		GSize First = Range.GetIndexFirst();
		GSize Last = Range.GetIndexLast();
		
		for (GSize I = First; I < Last; ++I)
		{
			Task->Execute(I, WorkerId);
		}

		// Task->ExecuteRange(Range, WorkerId);

		delete this;
		return 0;
	}

}